﻿using System;
using System.Collections.Generic;

namespace bianyi
{
    public enum Token
    {
        INVALID = 0,  // <invalid>

        TK_IDENT,    // <identifier>
        TK_EOF,      // <eof>
        LIT_INT,     // <integer>
        LIT_STR,     // <string>
        LIT_DOUBLE,  // <decimal>
        LIT_CHAR,    // <character>

        TK_BITAND,  // &
        TK_BITOR,   // |
        TK_BITNOT,  // ~
        TK_LOGAND,  // &&
        TK_LOGOR,   // ||
        TK_LOGNOT,  // !

        TK_PLUS,   // +
        TK_MINUS,  // -
        TK_TIMES,  // *
        TK_DIV,    // /
        TK_MOD,    // %

        TK_EQ,  // ==
        TK_NE,  // !=
        TK_GT,  // >
        TK_GE,  // >=
        TK_LT,  // <
        TK_LE,  // <=

        TK_ASSIGN,     // =
        TK_PLUS_AGN,   // +=
        TK_MINUS_AGN,  // -=
        TK_TIMES_AGN,  // *=
        TK_DIV_AGN,    // /=
        TK_MOD_AGN,    // %=
        TK_COMMA,      // ,
        TK_LPAREN,     // (
        TK_RPAREN,     // )
        TK_LBRACE,     // {
        TK_RBRACE,     // }
        TK_LBRACKET,   // [
        TK_RBRACKET,   // ]

        KW_IF,        // if
        KW_ELSE,      // else
        KW_TRUE,      // true
        KW_FALSE,     // false
        KW_WHILE,     // while
        KW_FOR,       // for
        KW_NULL,      // null
        KW_FUNC,      // func
        KW_RETURN,    // return
        KW_BREAK,     // break
        KW_CONTINUE,  // continue
    };

    public class Ast
    {
    }

    public class AstNode
    {
        public int line = -1;
        public int column = -1;

        public AstNode(int line, int column)
        {
            this.line = line;
            this.column = column;
        }

        public string astString()
        {
            return "AstNode()";
        }
    }

    public class Expression : AstNode
    {
        public Expression(int line, int column) : base(line, column)
        {
        }

        public virtual Value eval(Runtime rt, List<Context> ctxChain)
        {
            throw new NotImplementedException();
        }
    }

    public class AssignExpr : Expression
    {
        public Expression lhs;
        public Token opt;
        public Expression rhs;

        public AssignExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            Value rhs = this.rhs.eval(rt, ctxChain);
            if (lhs is IdentExpr)
            {
                string identName = ((IdentExpr)lhs).identName;
                foreach (var item in ctxChain)
                {
                    var vars = item.getVariable(identName);
                    if (vars != null)
                    {
                        vars.value = Interpreter.assignSwitch(this.opt, vars.value, rhs);
                        return rhs;
                    }
                }
                ctxChain[ctxChain.Count - 1].createVariable(identName, rhs);
            }
            else
            {
                Console.WriteLine("SyntaxError: can not assign to %s at line %d, col %d\n",
                      this.lhs, line, column);
            }
            return rhs;
        }
    }

    public class FunCallExpr : Expression
    {
        public string funcName;
        public List<Expression> args = new List<Expression>();

        //public FunCallExpr()
        //{
        //}
        public FunCallExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            var builtinFunc = rt.getBuiltinFunction(this.funcName);
            if (builtinFunc != null)
            {
                List<Value> arguments = new List<Value>();
                foreach (var item in this.args)
                {
                    arguments.Add(item.eval(rt, ctxChain));
                }
                return builtinFunc(rt, ctxChain, arguments);
            }
            // 如果找不到则抛出函数未定义异常
            Console.WriteLine(
                "RuntimeError: can not find function definition of %s in both " +
                "built-in " +
                "functions and user defined functions",
                this.funcName);
            return null;
        }
    }

    public class BinaryExpr : Expression
    {
        public BinaryExpr(int line, int column) : base(line, column)
        {
        }

        public Expression lhs;
        public Token opt;
        public Expression rhs;

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            Value lhs =
                this.lhs != null ? this.lhs.eval(rt, ctxChain) : new Value() { type = ValueType.Null };
            Value rhs =
                this.rhs != null ? this.rhs.eval(rt, ctxChain) : new Value() { type = ValueType.Null };
            Token opt = this.opt;

            if (lhs.type != ValueType.Null && rhs.type == ValueType.Null)
            {
                return Interpreter.calcUnaryExpr(lhs, opt, line, column);
            }

            return Interpreter.calcBinaryExpr(lhs, opt, rhs, line, column);
        }

    }

    public class IndexExpr : Expression
    {
        public string identName;
        public Expression index;

        public IndexExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            foreach (var item in ctxChain)
            {
                var vars = item.getVariable(identName);
                if (vars != null)
                {
                    var idx = this.index.eval(rt, ctxChain);

                    if (idx.type != ValueType.Int)
                    {
                        Console.WriteLine("TypeError: expects int type within indexing expression at line %d, col %d\n", line, column);
                    }
                    if ((int)idx.data >= ((Array)(vars.value.data)).Length)
                    {
                        Console.WriteLine("IndexError: index %d out of range at line %d, col %d\n", (int)idx.data, line, column);
                    }

                    return (Value)((Array)(vars.value.data)).GetValue((int)idx.data);
                }
            }
            Console.WriteLine("RuntimeError: use of undefined variable \"%s\" at line %d, col %d\n", identName, line, column);
            return null;
        }
    }

    public class IdentExpr : Expression
    {
        public string identName;

        public IdentExpr(int line, int column) : base(line, column)
        {
        }

        public IdentExpr(string identName, int line, int column) : base(line, column)
        {
            this.identName = identName;
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            foreach (var item in ctxChain)
            {
                if (item.getVariable(identName) != null)
                {
                    return item.getVariable(identName).value;
                }
            }
            Console.WriteLine("RuntimeError: use of undefined variable \"{0}\" at line {1}, col {2}\n", identName, line, column);
           
            return null;
        }
    }

    public class ArrayExpr : Expression
    {
        private List<Value> literal;

        public ArrayExpr(int line, int column) : base(line, column)
        {
        }

        private Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Array, literal);
        }
    }

    public class IntExpr : Expression
    {
        public int literal;

        public IntExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Int, literal);
        }
    }

    public class StringExpr : Expression
    {
        public string literal;

        public StringExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.String, literal);
        }
    }

    public class DoubleExpr : Expression
    {
        public double literal;

        public DoubleExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Double, literal);
        }
    }

    public class NullExpr : Expression
    {
        public NullExpr(int line, int column) : base(line, column)
        {
        }

        private Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Null, null);
        }
    }

    public class BoolExpr : Expression
    {
        public bool literal;

        public BoolExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Bool, literal);
        }
    }

    public class CharExpr : Expression
    {
        public char literal;

        public CharExpr(int line, int column) : base(line, column)
        {
        }

        public override Value eval(Runtime rt, List<Context> ctxChain)
        {
            return new Value(ValueType.Char, literal);
        }
    }

    public class Statement : AstNode
    {
        public Statement(int line, int column) : base(line, column)
        {
        }

        public virtual ExecResult interpret(Runtime rt, List<Context> ctxChain)
        { return null; }
    }

    public class ContinueStmt : Statement
    {
        public ContinueStmt(int line, int column) : base(line, column)
        {
        }

        private ExecResult interpret(Runtime rt, List<Context> ctxChain)
        { return null; }
    }

    public class BreakStmt : Statement
    {
        public BreakStmt(int line, int column) : base(line, column)
        {
        }

        private ExecResult interpret(Runtime rt, List<Context> ctxChain)
        { return null; }
    }

    public class ExpressionStmt : Statement
    {
        public Expression expr;

        public ExpressionStmt(int line, int column) : base(line, column)
        {
        }

        public ExpressionStmt(Expression expr, int line, int column) : base(line, column)
        {
            this.expr = expr;
        }

        public override ExecResult interpret(Runtime rt, List<Context> ctxChain)
        {
            this.expr.eval(rt, ctxChain);
            return new ExecResult();
        }
    }

    public class IfStmt : Statement
    {
        internal Expression cond;
        internal Block block;
        internal Block elseBlock;
        //private int line;
        //private int column;

        public IfStmt(int line, int column) : base(line, column)
        {
        }

        public override ExecResult interpret(Runtime rt, List<Context> ctxChain)
        {
            ExecResult ret = new ExecResult();
            Value cond = this.cond.eval(rt, ctxChain);
            if (cond.type != ValueType.Bool)
            {
                Console.WriteLine("TypeError: expects bool type in while condition at line {0}, " + "col {1}\n", line, column);
                return null;
            }
            if (true == (bool)cond.data)
            {
                Interpreter.enterContext(ctxChain);
                foreach (var stmt in block.stmts)
                {
                    // std::cout << stmt->astString() << "\n";
                    ret = stmt.interpret(rt, ctxChain);
                    if (ret.execType == ExecutionResultType. ExecReturn)
                    {
                        break;
                    }
                    else if (ret.execType == ExecutionResultType.ExecBreak)
                    {
                        break;
                    }
                    else if (ret.execType == ExecutionResultType.ExecContinue)
                    {
                        break;
                    }
                }
                Interpreter.leaveContext(ctxChain);
            }
            else
            {
                if (elseBlock != null)
                {
                    Interpreter.enterContext(ctxChain);
                    foreach (var elseStmt in elseBlock.stmts)
                    {
                        // std::cout << stmt->astString() << "\n";
                        ret = elseStmt.interpret(rt, ctxChain);
                        if (ret.execType ==  ExecutionResultType.ExecReturn)
                        {
                            break;
                        }
                        else if (ret.execType == ExecutionResultType.ExecBreak)
                        {
                            break;
                        }
                        else if (ret.execType == ExecutionResultType.ExecContinue)
                        {
                            break;
                        }
                    }
                    Interpreter.leaveContext(ctxChain);
                }
            }
            return ret;
        }
    }

    public class ReturnStmt : Statement
    {
        internal object ret;
        private int line;
        private int column;

        public ReturnStmt(int line, int column) : base(line, column)
        {
        }
    }

    public class WhileStmt : Statement
    {
        public Expression cond;
        public Block block;
        //private int line;
        //private int column;

        public WhileStmt(int line, int column) : base(line, column)
        {
        }

        public override ExecResult interpret(Runtime rt, List<Context> ctxChain)
        {
            ExecResult ret=new ExecResult();
            Value cond = this.cond.eval(rt, ctxChain);

            Interpreter.enterContext(ctxChain);
            while (true == (bool)cond.data)
            {
                foreach (var stmt in block.stmts)
                {
                    // std::cout << stmt->astString() << "\n";
                    ret = stmt.interpret(rt, ctxChain);
                    if (ret.execType == ExecutionResultType. ExecReturn)
                    {
                        goto outside;
                    }
                    else if (ret.execType == ExecutionResultType.ExecBreak)
                    {
                        // Disable propagating through the whole chain
                        ret.execType = ExecutionResultType.ExecNormal;
                        goto outside;
                    }
                    else if (ret.execType == ExecutionResultType.ExecContinue)
                    {
                        // Disable propagating through the whole chain
                        ret.execType = ExecutionResultType.ExecNormal;
                        break;
                    }
                }
                cond = this.cond.eval(rt, ctxChain);
                if (!(cond.data is bool))
                {
                    Console.WriteLine(
                        "TypeError: expects bool type in while condition at line {0}, "+
                                "col {1}\n",
                        line, column);
                }
            }

        outside:
            Interpreter.leaveContext(ctxChain);
            return ret;
        }

   
    }

    public class ExecResult
    {
        public ExecResult()
        {
            execType = ExecutionResultType.ExecNormal;
        }
        public ExecResult(ExecutionResultType execType)
        {
            this.execType = execType;
        }
        public ExecResult(ExecutionResultType execType, Value retValue)
        {
            this.execType = execType;
            this.retValue = retValue;
        }
        public ExecutionResultType execType;
        public Value retValue;
    }
}